/**
 *  @file QuickParser.cpp
 */
#include "QuickParser.h"
#include "../../common/CppMemPool.h"
#include "../../io/BufferedReader.h"
#include "../../io/BufferedReader.h"
#include "../../convert/Convert.h"
#include "../../os/debug/Debug.h"

using namespace cppflib::io;
using namespace cppflib::os;
using namespace cppflib::collections;
using namespace cppflib::lang;

namespace cppflib
{

namespace json
{

QuickParser::QuickParser(void)
{
   this->pReader = NULL;
   this->pTokenizer = NULL;
   this->pHandler = NULL;
   this->pObjectNameStack = NULL;
   this->markErrorForDupName = true;
}

PRIVATE const int READERBUFSIZE = 16;

/**
 *  @param [in] errorForDupName -- true to consider error when duplicated name is found within
 *                                 the same JSON object
 */
QuickParser::QuickParser(Reader &reader, JSONHandler &handler, bool errorForDupName)
{
   this->pReader = new BufferedReader(&reader, READERBUFSIZE);
   cppmempool::Retain(this->pReader);
   this->pTokenizer = new JSONTokenizer(this->pReader);
   cppmempool::Retain(this->pTokenizer);
   this->pHandler = &handler;
   cppmempool::Retain(this->pHandler);
   this->pHandler->ClearErrorWarning();
   this->pObjectNameStack = new StackCppObjT<HashtableT<CFString, lang::Boolean> >(false);
   cppmempool::Retain(this->pObjectNameStack);
   this->markErrorForDupName = errorForDupName;
}

QuickParser::~QuickParser(void)
{
   if (this->pReader)
      cppmempool::Release(this->pReader);
   if (this->pTokenizer)
      cppmempool::Release(this->pTokenizer);
   if (this->pHandler)
      cppmempool::Release(this->pHandler);
   if (this->pObjectNameStack)
      cppmempool::Release(this->pObjectNameStack);
}

/**
 *  Notify handler
 */
void QuickParser::NotifyErrorToHandler(JSONHandler::jsonhandlererror_t err, pcwstr_t pszFormat, ...)
{
   CFString s;
   s.__InternalFormat(&pszFormat, 0);
   
   pHandler->MarkErrorWarning(err);
   switch(err) {
      case JSONHandler::HERR_WARN: pHandler->Warning(s); break;
      case JSONHandler::HERR_ERROR: pHandler->Error(s); break;
      case JSONHandler::HERR_FATAL: pHandler->FatalError(s); break;
   }
}

bool QuickParser::ParseJSONValue()
{
   CFString token;
   JSONTokenizer::tokentype_t tokenType = pTokenizer->GetToken(token);
   return __ParseJSONValue(tokenType, token);
}

bool QuickParser::__ParseJSONValue(JSONTokenizer::tokentype_t tokenType, const CFString &token)
{
   bool isOK = true;
   
   switch (tokenType) {
      case JSONTokenizer::JN_STRING: pHandler->ValueString(token); break; 
      case JSONTokenizer::JN_INT32: pHandler->ValueInt(convert::ToInt32(token)); break; 
      case JSONTokenizer::JN_INT64: pHandler->ValueInt64(convert::ToInt64(token)); break; 
      case JSONTokenizer::JN_DOUBLE: pHandler->ValueDouble(convert::ToDouble(token)); break; 
      case JSONTokenizer::JN_TRUE: pHandler->ValueBoolean(true); break;
      case JSONTokenizer::JN_FALSE: pHandler->ValueBoolean(false); break;
      case JSONTokenizer::JN_NULL: pHandler->ValueNull(); break;
      case JSONTokenizer::JN_ARRAYSTART: isOK = ParseJSONArray(); break;
      case JSONTokenizer::JN_OBJSTART: isOK = ParseJSONObject(); break;
      case JSONTokenizer::JN_INT64OVERFLOW: 
         {
            NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("Integer overflow on line %d"), pTokenizer->GetLineNumber());
            isOK = false;
         }
         break;
      default:
         {
            NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("Unkown token (%s) on line %d; string, integer, float, boolean, null, array or object expected"), static_cast<pcwstr_t>(token), pTokenizer->GetLineNumber());
            isOK = false;
         }
         break;
   }

   return isOK;
}

bool QuickParser::ParseJSONArray()
{
   pHandler->IncrementArrayNestedLevel();
   pHandler->StartArray();

   bool isOK = true;
   CFString token;
   JSONTokenizer::tokentype_t tokenType = pTokenizer->GetToken(token);

   if (tokenType == JSONTokenizer::JN_ARRAYEND) { // empty array
      pHandler->EndArray();
      pHandler->DecrementArrayNestedLevel();
   }   
   else {

	   while (true) {
		  if (!__ParseJSONValue(tokenType, token)) {
			 isOK = false;
			 break;
		  }

		  tokenType = pTokenizer->GetToken(token);
		  if (tokenType == JSONTokenizer::JN_ARRAYEND) { // end of array
			 pHandler->EndArray();
			 pHandler->DecrementArrayNestedLevel();
			 break;
		  }
		  else {
			 if (tokenType != JSONTokenizer::JN_COMMA) { // next value in array
				NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("Unkown token (%s) on line %d; ',' or ']' expected"), static_cast<pcwstr_t>(token), pTokenizer->GetLineNumber());
				isOK = false;
				break;
			 }
		  }

          tokenType = pTokenizer->GetToken(token); // for next round

	   } // end of while

   } 
   
   return isOK;
}

void QuickParser::MarkObjectStart()
{
   pHandler->IncrementObjectNestedLevel();
   pHandler->StartObject();
   pObjectNameStack->Push(new HashtableT<CFString, Boolean>()); // create a table to check name uniqueness
}

void QuickParser::MarkObjectEnd()
{
   pObjectNameStack->PopAndRemove();
   pHandler->EndObject();
   pHandler->DecrementObjectNestedLevel();
}

/**
 *  Check whether the provided name is unique within the current object
 *
 *  @return true -- new name is unique within the same object OR markErrorForDupName is false
 *  @return false -- new name is duplicated
 */
bool QuickParser::CheckNameUniquenessWithinObject(const CFString &newName)
{
   HashtableT<CFString, Boolean> *pCurObjNameTbl = pObjectNameStack->Peek();
   if (pCurObjNameTbl->ContainsKey(newName)) {
      JSONHandler::jsonhandlererror_t err = markErrorForDupName ? JSONHandler::HERR_ERROR : JSONHandler::HERR_WARN;
      NotifyErrorToHandler(err, _S("Duplicated name (%s) on line %d"), static_cast<pcwstr_t>(newName), pTokenizer->GetLineNumber());
      return !markErrorForDupName;
   }
   
   // add new name to current table
   pCurObjNameTbl->Set(new CFString(newName), new Boolean(true));
   return true;
}

bool QuickParser::ParseJSONObject()
{
   MarkObjectStart();

   bool isOK = true;
   CFString token, curNVPairName;
   JSONTokenizer::tokentype_t tokenType = pTokenizer->GetToken(token);

   if (tokenType == JSONTokenizer::JN_OBJEND) {
      MarkObjectEnd(); // empty object
   }
   else if (tokenType == JSONTokenizer::JN_STRING) {
      while (true) {
         curNVPairName = token;
         if (!CheckNameUniquenessWithinObject(curNVPairName)) {
            isOK = false;
            break;
         }
         pHandler->SetCurNVPairName(curNVPairName); // current object name
         pHandler->StartNVPair(curNVPairName);

         tokenType = pTokenizer->GetToken(token);  // colon 
         if (tokenType != JSONTokenizer::JN_COLON) {
            NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("Unkown token (%s) on line %d; ':' expected"), static_cast<pcwstr_t>(token), pTokenizer->GetLineNumber());
            isOK = false;
            break;
         }

         if (!ParseJSONValue()) { // value
            isOK = false;
            break;
         }
         pHandler->SetCurNVPairName(curNVPairName); // recover object name (may have stepped into a nested object)
         pHandler->EndNVPair();

         tokenType = pTokenizer->GetToken(token);  
         if (tokenType == JSONTokenizer::JN_OBJEND) { // end of object
            MarkObjectEnd();
            break;
         }
         else if (tokenType == JSONTokenizer::JN_COMMA) { // next name-value pair
            tokenType = pTokenizer->GetToken(token);
            if (tokenType != JSONTokenizer::JN_STRING) {
               NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("Unkown token (%s) on line %d; string token expected"), static_cast<pcwstr_t>(token), pTokenizer->GetLineNumber());
               isOK = false;
               break;
            }
         }
         else {
            NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("Unkown token (%s) on line %d; ',' or '}' expected"), static_cast<pcwstr_t>(token), pTokenizer->GetLineNumber());
            isOK = false;
            break;
         }

      } // end of while
   }
   else {
      NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("Unkown token (%s) on line %d; string or '}' expected"), static_cast<pcwstr_t>(token), pTokenizer->GetLineNumber());
      isOK = false;
   }

   return isOK;
}

void QuickParser::StartParsing()
{
   CFString token;
   bool rootObjectFound = false;

   while(true) {
      JSONTokenizer::tokentype_t tokenType = pTokenizer->GetToken(token);

      if (tokenType == JSONTokenizer::JN_OBJSTART) {
         if (rootObjectFound) {
            NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("More than one root JSON object on line %d"), pTokenizer->GetLineNumber());
            break;
         }
         else {
            rootObjectFound = true;
            if (!ParseJSONObject())
               break;
         }
      }
      else if (tokenType == JSONTokenizer::JN_READEREND) {
         if (!rootObjectFound)
            NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("No JSON object found"));
         break;
      }
      else if (tokenType == JSONTokenizer::JN_UNKNOWN) {
         NotifyErrorToHandler(JSONHandler::HERR_ERROR, _S("Error parsing JSON on line %d"), pTokenizer->GetLineNumber());
         break;
      }
   } // end of while

}

} // end of namespace json

} // end of namespace cppflib
